//
// Created by hunan on 25-3-22.
//

#include "tls_socket_server_connection.h"

#include "description_module/description_module.h"
#include "socket_module/socket_address.h"
#include "socket_module/socket_channel.h"
#include "socket_module/socket_description.h"
#include "socket_module/socket_server_connection.h"
#include "socket_module/tls_socket_operate.h"

#include <arpa/inet.h>
#include <cerrno>
#include <cstdio>
#include <memory>
#include <netinet/in.h>
#include <sys/socket.h>
#include <unistd.h>

class TlsSocketServerChannel : public SocketChannel
{
public:
    explicit TlsSocketServerChannel(Loop * loop, const std::shared_ptr<Description> & descri, TlsSocketServer * server);
    ~TlsSocketServerChannel() override = default;
    void OnErrorEvent(std::shared_ptr<SocketChannel> channel, std::shared_ptr<Description> descri) override;
    void OnReadEvent(std::shared_ptr<SocketChannel> channel, std::shared_ptr<Description> descri) override;
    void OnWriteEvent(std::shared_ptr<SocketChannel> channel, std::shared_ptr<Description> descri) override;

private:
    TlsSocketServer * m_server;
};

class TlsSocketServerConnectionChannel : public SocketChannel
{
public:
    explicit TlsSocketServerConnectionChannel(Loop * loop, const std::shared_ptr<Description> & descri,
                                              TlsSocketServerConnection * connection);
    ~TlsSocketServerConnectionChannel() override = default;
    void OnErrorEvent(std::shared_ptr<SocketChannel> channel, std::shared_ptr<Description> descri) override;
    void OnReadEvent(std::shared_ptr<SocketChannel> channel, std::shared_ptr<Description> descri) override;
    void OnWriteEvent(std::shared_ptr<SocketChannel> channel, std::shared_ptr<Description> descri) override;

private:
    TlsSocketServerConnection * m_connection;
};

TlsSocketServerChannel::TlsSocketServerChannel(Loop * loop, const std::shared_ptr<Description> & descri,
                                               TlsSocketServer * server)
    : SocketChannel(loop, descri)
{
    m_server = server;
}

void TlsSocketServerChannel::OnReadEvent(std::shared_ptr<SocketChannel> channel, std::shared_ptr<Description> desc)
{
    printf("description %d tls tcp server trigger read event.\n", desc->DescriptionData());
    while (true)
    {
        sockaddr_in sai    = {};
        socklen_t          len    = sizeof(sai);
        int                result = accept(desc->DescriptionData(), reinterpret_cast<struct sockaddr *>(&sai), &len);
        if (result <= 0)
        {
            break;
        }
        if (m_server->m_on_conn_cb)
        {
            SocketAddress addr              = SocketAddress::Address(sai);
            auto          socket_description = std::make_shared<SocketDescription>(result);
            auto          connection = std::shared_ptr<SocketServerConnection>(new TlsSocketServerConnection(m_server, socket_description));
            connection->UpdateConnState(SocketServer::EmConnectionHandshaking);
            connection->UpdatePeerAddress(addr);
            {
                SocketServerConnection::ClientOption option = {
                    .m_enable_nonblock = m_server->m_option.m_enable_nonblock,
                    .m_crt_file = m_server->m_option.m_crt_file,
                    .m_private_key_file = m_server->m_option.m_private_key_file,
                    .m_ca_file  = m_server->m_option.m_ca_file,
                };
                connection->UpdateOption(option);
            }
            connection->SetOnConnect(
                [this](const std::shared_ptr<SocketServerConnection>& conn) { m_server->m_on_conn_cb(conn); });
            connection->SetOnMessage(
                [this](const std::shared_ptr<SocketServerConnection> & conn, int ret, SocketServer::Message & message) {
                    if (m_server->m_on_message_cb)
                    {
                        m_server->m_on_message_cb(conn, ret, message);
                    }
                });
            connection->SetOnDisConn([this](const std::shared_ptr<SocketServerConnection> & conn, int ret) {
                m_server->m_connection_set.erase(conn);
                if (m_server->m_on_dis_conn_cb)
                {
                    m_server->m_on_dis_conn_cb(conn, ret);
                }
            });
            connection->AsyncWrite(nullptr, 0);
            m_server->m_connection_set.insert(connection);
        }
        else
        {
            close(result);
        }
        if (!m_server->m_option.m_enable_nonblock)
        {
            break;
        }
    }
    channel->ModEvent(EmIoOneShot | EmIoIn);
}

void TlsSocketServerChannel::OnErrorEvent(std::shared_ptr<SocketChannel>, std::shared_ptr<Description>)
{
}

void TlsSocketServerChannel::OnWriteEvent(std::shared_ptr<SocketChannel>, std::shared_ptr<Description>)
{
}

TlsSocketServerConnectionChannel::TlsSocketServerConnectionChannel(Loop * loop, const std::shared_ptr<Description> & description,
                                                                   TlsSocketServerConnection * connection)
    : SocketChannel(loop, description)
{
    m_connection = connection;
}

void TlsSocketServerConnectionChannel::OnErrorEvent(std::shared_ptr<SocketChannel> channel,
                                                    std::shared_ptr<Description>   description)
{
    printf("description %d tls tcp socket connection trigger error event.\n", description->DescriptionData());
    m_connection->Close();
    m_connection->m_on_dis_conn_cb(m_connection->shared_from_this(), -1);
}

void TlsSocketServerConnectionChannel::OnWriteEvent(std::shared_ptr<SocketChannel> channel,
                                                    std::shared_ptr<Description>   description)
{
    printf("description %d tls tcp socket connection trigger write event.\n", description->DescriptionData());
    switch (m_connection->ConnectionState())
    {
        case SocketServer::EmConnectionConnected:
        {
            if (m_connection->m_write_buffer.BufferDataSize() > 0)
            {
                int result = m_connection->m_tls_operate->Write(m_connection->m_write_buffer.BufferData(),
                                                                m_connection->m_write_buffer.BufferDataSize());
                if (result < 0)
                {
                    OnErrorEvent(channel, description);
                }
                else
                {
                    m_connection->m_write_buffer.RemoveSize(result);
                }
            }
            int event = m_connection->m_write_buffer.BufferDataSize() > 0 ? EmIoOut : 0;
            ModEvent(event | EmIoIn | EmIoOneShot | EmIoRdHup);
            break;
        }
        case SocketServer::EmConnectionHandshaking:
        {
            TlsSocketOperate::TlsOption option = {};
            {
                option.m_ca_file = m_connection->m_client_option.m_ca_file;
                option.m_public_crt_file = m_connection->m_client_option.m_crt_file;
                option.m_private_key_file = m_connection->m_client_option.m_private_key_file;
            }
            int result = m_connection->m_tls_operate->ServerConnection(option);
            if (result < 0)
            {
                OnErrorEvent(channel, description);
            }
            else if (result == 0)
            {
                ModEvent(EmIoOut | EmIoOneShot | EmIoRdHup);
            }
            else
            {
                printf("description %d update to connected.\n", description->DescriptionData());
                m_connection->UpdateConnState(SocketServer::EmConnectionConnected);
                ModEvent(EmIoIn | EmIoOneShot | EmIoRdHup);
                if (m_connection->m_on_connect_cb)
                {
                    m_connection->m_on_connect_cb(m_connection->shared_from_this());
                }
            }
            break;
        }
        default:
            break;
    }
}

void TlsSocketServerConnectionChannel::OnReadEvent(std::shared_ptr<SocketChannel> channel,
                                                   std::shared_ptr<Description>   description)
{
    printf("description %d tls tcp socket connection trigger read event.\n", description->DescriptionData());
    if (m_connection->ConnectionState() == SocketServer::EmConnectionConnected)
    {
        while (true)
        {
            if (m_connection->m_read_buffer.BufferFreeSize() > 65535)
            {
                m_connection->m_read_buffer.ExpendBuffer(65535);
            }
            int result = m_connection->m_tls_operate->Read(m_connection->m_read_buffer.BufferEnd(),
                                                           m_connection->m_read_buffer.BufferFreeSize());
            if (result < 0)
            {
                OnErrorEvent(channel, description);
                break;
            }
            if (result == 0)
            {
                break;
            }
            m_connection->m_read_buffer.MovePosition(result);
            if (!m_connection->m_client_option.m_enable_nonblock && m_connection->m_read_buffer.BufferFreeSize() > 0)
            {
                break;
            }
        }
        if (m_connection->ConnectionState() == SocketServer::EmConnectionConnected)
        {
            if (m_connection->m_on_message_cb && m_connection->m_read_buffer.BufferDataSize())
            {
                SocketServer::Message message = {};
                message.m_data = m_connection->m_read_buffer.BufferData();
                message.m_data_length = m_connection->m_read_buffer.BufferDataSize();
                message.m_use_length = 0;
                m_connection->m_on_message_cb(m_connection->shared_from_this(), 0, message);
                m_connection->m_read_buffer.RemoveSize(message.m_use_length);
            }
            int event = m_connection->m_write_buffer.BufferDataSize() > 0 ? EmIoOut : 0;
            ModEvent(event | EmIoOneShot | EmIoIn | EmIoRdHup);
        }
    }
}

TlsSocketServer::TlsSocketServer(Loop * loop) : SocketServer(), m_option()
{
    m_loop = loop;
}

int TlsSocketServer::BindAddress(const Option & option)
{
    m_option      = option;
    m_description = std::make_shared<SocketDescription>(SocketDescription::TCPSOCKET);
    int result    = m_description->CreateDescription();
    if (result < -1)
    {
        return -1;
    }
    sockaddr_in sai = {};
    sai.sin_family         = AF_INET;
    if (m_option.m_listen_port > 0)
    {
        sai.sin_port = htons(m_option.m_listen_port);
    }
    if (!m_option.m_bind_addr.empty())
    {
        inet_pton(sai.sin_family, m_option.m_bind_addr.c_str(), &sai.sin_addr.s_addr);
    }
    else
    {
        sai.sin_addr.s_addr = INADDR_ANY;
    }
    result = bind(m_description->DescriptionData(), reinterpret_cast<sockaddr *>(&sai), sizeof(sai));
    if (result < 0)
    {
        return -1;
    }
    m_channel = std::make_shared<TlsSocketServerChannel>(m_loop, m_description, this);
    return 0;
}

int TlsSocketServer::Listen()
{
    if (m_description->DescriptionData() > 0)
    {
        auto self = shared_from_this();
        m_channel->Post([self] {
            listen(self->m_description->DescriptionData(), 1024);
            self->m_operate = std::make_shared<TlsSocketOperate>(self->m_description, TlsSocketOperate::EmOperateType::EmOperateTls);
            self->m_channel->AddEvent(EmIoOneShot | EmIoIn);
        });
        return 0;
    }
    return -1;
}

Loop * TlsSocketServer::GetLoop() const
{
    return m_loop;
}

TlsSocketServerConnection::TlsSocketServerConnection(TlsSocketServer * server, const std::shared_ptr<Description> & description)
{
    m_server      = server;
    m_description = description;
    m_channel     = std::make_shared<TlsSocketServerConnectionChannel>(m_server->GetLoop(), m_description, this);
}

int TlsSocketServerConnection::AsyncRead()
{
    if (ConnectionState() == SocketServer::EmConnectionConnected)
    {
        auto self = shared_from_this();
        m_channel->Post([self] {
            const int event = self->m_write_buffer.BufferDataSize() > 0 ? EmIoOut : 0;
            if (self->m_channel->Index() > 0)
            {
                self->m_channel->ModEvent(event | EmIoIn | EmIoOneShot | EmIoRdHup | EmIoErr);
            }
            else
            {
                self->m_channel->AddEvent(event | EmIoIn | EmIoOneShot | EmIoRdHup | EmIoErr);
            }
        });
        return 0;
    }
    return -1;
}

int TlsSocketServerConnection::AsyncWrite(const uint8_t * data, int data_length)
{
    if (ConnectionState() == SocketServer::EmConnectionConnected)
    {
        m_write_buffer.PushDataToBuffer(data, data_length);
        auto self = shared_from_this();
        m_channel->Post([self] { self->AsyncRead(); });
        return 0;
    }
    if (ConnectionState() == SocketServer::EmConnectionHandshaking)
    {
        m_tls_operate = std::make_shared<TlsSocketOperate>(m_description, TlsSocketOperate::EmOperateType::EmOperateTlsServer);
        auto self     = shared_from_this();
        m_channel->Post([self] {
            if (self->m_channel->Index() > 0)
            {
                self->m_channel->ModEvent(EmIoOut | EmIoRdHup | EmIoOneShot);
            }
            else
            {
                self->m_channel->AddEvent(EmIoOut | EmIoRdHup | EmIoOneShot);
            }
        });
        return 0;
    }
    return -1;
}

void TlsSocketServerConnection::Close()
{
    if (m_description)
    {
        UpdateConnState(SocketServer::EmConnectionDisConnected);
        if (m_channel)
        {
            auto self = shared_from_this();
            m_channel->Post([self] {
                if (self->m_channel->Index() > 0)
                {
                    self->m_channel->DelEvent();
                }
                self->m_description->CloseDescription();
            });
        }
    }
}

void TlsSocketServerConnection::UpdateOption(const ClientOption & option)
{
    m_client_option = option;
}

